Skip to content

Surface relayer sponsorship signal end-to-end (additive, non-breaking)#1006

Closed
matt416 wants to merge 3 commits into
masterfrom
matt416/pr-breaking-change-impact
Closed

Surface relayer sponsorship signal end-to-end (additive, non-breaking)#1006
matt416 wants to merge 3 commits into
masterfrom
matt416/pr-breaking-change-impact

Conversation

@matt416
Copy link
Copy Markdown
Contributor

@matt416 matt416 commented May 28, 2026

Summary

Fixes the /FeeOptions misclassification described in our internal bug report: a real subsidy and a swallowed /FeeOptions error currently produce byte-identical option shapes, so any consumer using !feeOption as the sponsorship discriminator labels failed quotes as "free gas". This PR makes the two cases distinguishable end-to-end without breaking any existing consumer.

  • @0xsequence/relayerfeeOptions now forwards the server's sponsored: boolean; both feeOptions and feeTokens mark swallowed errors with failed: true. The Relayer interface and all impls (Rpc, Sequence, Local, EIP6963, Pk) are widened to match.
  • @0xsequence/wallet-wdkStandardRelayerOption gains optional sponsored / failed, populated on both construction branches in transactions.ts; isStandardRelayerOption / isERC4337RelayerOption are now re-exported.
  • @0xsequence/dapp-client — adds DappClient.isSponsored(chainId, txs) (and ChainSessionManager.isSponsored(calls)), which returns true only when the relayer explicitly reports sponsorship.

Downstream consumers should switch sponsorship classification from !feeOption inference to sponsored === true (or isSponsored()) so a real subsidy is no longer indistinguishable from a swallowed error. All three packages get minor changesets — additive types, no behavior change for unchanged code paths.

Test plan

  • Relayer unit tests: sponsored:true / sponsored:false propagation + failed:true on feeOptions and feeTokens error paths (124/124 vitest passing)
  • WDK integration test: asserts sponsored === false and failed === undefined on the LocalRelayer/PkRelayer path
  • Monorepo typecheck + lint + prettier (pre-commit hook ran clean on every commit)
  • CI: full monorepo test suite incl. anvil-fork-based wallet-core tests (skipped locally — needs pnpm test:anvil)
  • Manual smoke against a consumer app: confirm option.sponsored === true for a genuinely sponsored tx and option.failed === true for a forced /FeeOptions 400 (e.g. Polygon Morpho capped vault, Aave insufficient-balance deposit)

🤖 Generated with Claude Code

matt416 and others added 3 commits May 28, 2026 14:03
`RpcRelayer.feeOptions` now forwards the server's `sponsored: boolean` to
callers, and both `feeOptions` and `feeTokens` mark their swallowed-error
returns with `failed: true`. The `Relayer` interface and all bundled
implementations (Rpc, Sequence, Local, EIP6963, Pk) are widened to match.

Additive change: existing consumers ignoring the new fields are unaffected.
Downstream sponsorship classifiers should switch from `!feeOption` inference
to `sponsored === true` so a real subsidy is no longer indistinguishable
from a swallowed `/FeeOptions` error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`StandardRelayerOption` gains optional `sponsored` and `failed` fields,
populated on both construction branches in `transactions.ts` from the
relayer SDK's new `feeOptions` return. `isStandardRelayerOption` /
`isERC4337RelayerOption` are re-exported so consumers can narrow before
reading the new fields.

UI consumers that classified sponsorship by "no fee option attached"
should switch to `sponsored === true` to distinguish a real subsidy from
a swallowed `/FeeOptions` error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`DappClient.isSponsored(chainId, transactions)` and
`ChainSessionManager.isSponsored(calls)` return true only when the
relayer's `/FeeOptions` endpoint explicitly reports sponsorship; any
error, network failure, or absence of sponsorship returns false. A true
result is always safe to surface as "free gas" in UI.

Prefer this over inferring sponsorship from an empty `getFeeOptions`
array — a swallowed `/FeeOptions` error produces the same empty shape as
a real subsidy. `getFeeOptions` is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@matt416 matt416 requested a review from a team May 28, 2026 19:05
@matt416 matt416 requested a review from a team as a code owner May 28, 2026 19:05
@matt416 matt416 closed this May 28, 2026
@matt416 matt416 deleted the matt416/pr-breaking-change-impact branch May 28, 2026 19:08
@matt416 matt416 changed the title Surface sponsorship signal end-to-end (non-breaking) Surface relayer sponsorship signal end-to-end (additive, non-breaking) May 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e34cf7d09

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

calls: Payload.Call[],
data?: Hex.Hex,
): Promise<{ options: FeeOption[]; quote?: FeeQuote }>
): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep sponsored optional for relayer compatibility

This makes the public Relayer contract require every feeOptions implementation to return sponsored, so any downstream custom relayer or typed mock that was valid before (Promise<{ options; quote? }>), even if it does not care about the new signal, is no longer assignable to Relayer. Since all new consumers already treat only sponsored === true as sponsored, making this field optional (and defaulting missing values to false at read sites) would preserve the advertised non-breaking/additive change while still distinguishing explicit sponsorship.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant